using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using ShapeRenderingSample;
using Px = PhysX;

namespace physxXNA
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private Matrix world;
        private ArcBallCamera camera;

        private int xLastState;
        private int yLastState;
        private bool firstClick = false;
        private bool zoomClick = false;

        private SpriteFont font;

        VertexPositionColor[] vertices;
        VertexBuffer vertexBuffer;
        BasicEffect basicEffect;

        private ParticleEmitter emmiter;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        #region Game
     
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            DebugShapeRenderer.Initialize(GraphicsDevice);

            world = Matrix.Identity;

            //Camera
            camera = new ArcBallCamera(MathHelper.ToRadians(45.0f), graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 0.3f, 10000f);
            camera.Zoom += 1000.0f;
            xLastState = Mouse.GetState().X;
            yLastState = Mouse.GetState().Y;
            

            IsMouseVisible = true;

            basicEffect = new BasicEffect(graphics.GraphicsDevice);

            InitializePhysics();

            CreateGround();
            //CreateBox();
            //CreateSphere();
            //CreateParticles();

            emmiter = new ParticleEmitter(physics, scene, new Px.Math.Vector3(0, 0, 0), new Px.Math.Vector3(0, 0, 0), 100);
            emmiter.SpawnParticle();
            emmiter.SpawnParticle();
            emmiter.EmmisionRate = 4;
            emmiter.ParticleSystem.ExternalAcceleration = new Px.Math.Vector3(0, 10, 0);
            
            //emmiter.CreateParticles();

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load <SpriteFont>("hudFont");


            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            int x, y;

            if (Mouse.GetState().LeftButton == ButtonState.Pressed)
            {
                x = Mouse.GetState().X;
                y = Mouse.GetState().Y;

                if (firstClick)
                {
                    xLastState = x;
                    yLastState = y;
                    firstClick = false;
                }

                float xDelta = (xLastState - x) * 0.5f;
                float yDelta = (yLastState - y) * 0.5f;

                camera.HorizontalAngle += MathHelper.ToRadians(xDelta);
                camera.VerticalAngle += MathHelper.ToRadians(yDelta);
                //camera.Update(gameTime);

                xLastState = x;
                yLastState = y;
            }

            if (Mouse.GetState().LeftButton == ButtonState.Released)
            {
                firstClick = true;
            }

            if (Mouse.GetState().RightButton == ButtonState.Pressed)
            {
                x = Mouse.GetState().X;
                y = Mouse.GetState().Y;

                if (zoomClick)
                {
                    xLastState = x;
                    yLastState = y;
                    zoomClick = false;
                }

                float xDelta = (xLastState - x) * 0.5f;
                float yDelta = (yLastState - y) * 0.5f;

                //camera.HorizontalAngle += MathHelper.ToRadians(xDelta);
                //camera.VerticalAngle += MathHelper.ToRadians(yDelta);
                camera.Zoom += yDelta;
                //camera.Update(gameTime);                

                xLastState = x;
                yLastState = y;
            }

            if (Mouse.GetState().RightButton == ButtonState.Released)
            {
                zoomClick = true;
            }

            if (Keyboard.GetState().IsKeyDown(Keys.Space))
            {
                Random rand = new Random();

                //box.AddForce(new Px.Math.Vector3(100, 1000, 0));
                box.AddTorque(new Px.Math.Vector3(0, 0, 1000));

                /*Px.Math.Vector3[] forces = new Px.Math.Vector3[ps.MaximumParticles];
                int[] index = new int[ps.MaximumParticles];

                for(int i = 0; i < ps.MaximumParticles; i++)
                {
                    forces[i] = new Px.Math.Vector3((float)rand.NextDouble(),
                                                    (float)rand.NextDouble(),
                                                    (float)rand.NextDouble());
                    index[i] = i;
                }

                ps.AddForces(forces, index, Px.ForceMode.Impulse); */        
            }

            camera.Update(gameTime);

            emmiter.Update(gameTime);
            scene.Simulate((float)gameTime.ElapsedGameTime.TotalSeconds);
            scene.FetchResults(true);
            
           

            base.Update(gameTime);
        }

        List<Vector3> particlePostions;

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            RasterizerState state = new RasterizerState();
            state.CullMode = CullMode.None;

            GraphicsDevice.RasterizerState = state;

            spriteBatch.Begin();
            spriteBatch.DrawString(font, "TEST", new Vector2(10, 10), Color.White);
            spriteBatch.End();

            //DebugShapeRenderer.AddBoundingBox(new BoundingBox(new Vector3(0,0,0), new Vector3(1,1,1)), Color.Red);
            //DebugShapeRenderer.Draw(gameTime, camera.viewMatrix, camera.projectionMatrix);

            
            RenderBufferData(scene.GetRenderBuffer());

            if (particlePostions != null)
            {
                foreach (Vector3 v in particlePostions)
                {
                    DebugShapeRenderer.AddBoundingSphere(new BoundingSphere(v, 0.5f), Color.Blue);
                }
            }

            DebugShapeRenderer.Draw(gameTime, camera.viewMatrix, camera.projectionMatrix);



            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }

        #endregion

        #region Physx

        Px.Foundation foundation;
        Px.DefaultErrorCallback errorCallback;
        Px.Physics physics;
        Px.Scene scene;

        Px.RigidStatic ground;
        Px.RigidDynamic box;
        Px.RigidDynamic sphere;
        Px.ParticleSystem ps;

        private void InitializePhysics()
        {
            errorCallback = new Px.DefaultErrorCallback();
            foundation = new Px.Foundation(errorCallback);
            physics = new Px.Physics(foundation, true);

            Px.SceneDesc sceneDesc = new Px.SceneDesc();
            sceneDesc.Gravity = new Px.Math.Vector3(0f, -9.81f, 0f);

            scene = physics.CreateScene(sceneDesc);

            scene.SetVisualizationParameter(Px.VisualizationParameter.Scale, 0.5f);
            scene.SetVisualizationParameter(Px.VisualizationParameter.CollisionShapes, true);
            scene.SetVisualizationParameter(Px.VisualizationParameter.JointLocalFrames, true);
            scene.SetVisualizationParameter(Px.VisualizationParameter.JointLimits, true);

            //scene.SetVisualizationParameter(Px.VisualizationParameter.ParticleSystem, true);
            scene.SetVisualizationParameter(Px.VisualizationParameter.ParticleSystemPosition, true);
            //scene.SetVisualizationParameter(Px.VisualizationParameter.ParticleSystemBounds, true);
            ///scene.SetVisualizationParameter(Px.VisualizationParameter.ParticleSystemCollisionNormal, true);
            //scene.SetVisualizationParameter(Px.VisualizationParameter.ParticleSystemBroadphaseBounds, true);
            scene.SetVisualizationParameter(Px.VisualizationParameter.ActorAxes, true);
        }

        private void CreateGround()
        {
            Px.Material groundMaterial = physics.CreateMaterial(0.1f, 0.1f, 1.0f);
            ground = physics.CreateRigidStatic();

            Px.PlaneGeometry planeGeometry = new Px.PlaneGeometry();

            ground.GlobalPose = Px.Math.Matrix.RotationZ(MathHelper.ToRadians(90.0f));
            ground.CreateShape(planeGeometry, groundMaterial);

            scene.AddActor(ground);

        }

        private void CreateBox()
        {
            Px.Material boxMaterial = physics.CreateMaterial(0.2f, 0.2f, 1.0f);
            Px.BoxGeometry geometry = new Px.BoxGeometry(1, 1, 1);

            box = physics.CreateRigidDynamic();
            box.CreateShape(geometry, boxMaterial);
            box.GlobalPose = Px.Math.Matrix.Identity * Px.Math.Matrix.Translation(0, 10, 0);
            box.SetMassAndUpdateInertia(10);

            scene.AddActor(box);

            box.AddTorque(new Px.Math.Vector3(0, 20, 0), Px.ForceMode.Acceleration, true);
            
        }

        private void CreateSphere()
        {
            Px.Material sphereMaterial = physics.CreateMaterial(0.2f, 0.2f, 0.0f);
            Px.SphereGeometry geometry = new Px.SphereGeometry(2.0f);

            sphere = physics.CreateRigidDynamic();
            sphere.CreateShape(geometry, sphereMaterial);
            sphere.GlobalPose = Px.Math.Matrix.Identity * Px.Math.Matrix.Translation(5, 10, 0);
            sphere.SetMassAndUpdateInertia(10);

            scene.AddActor(sphere);

            sphere.AddTorque(new Px.Math.Vector3(0, 20, 0), Px.ForceMode.Acceleration, true);

        }

        private void CreateParticles()
        {
            int d = 10;
            ps = physics.CreateParticleSystem(d * d * d);
            

            Px.Math.Vector3[] particlePositions = new Px.Math.Vector3[d*d*d];
            int[] indexBuffer = new int[d*d*d];

            /*for (int i = 0; i < pCount; i++)
            {
                particlePositions[i] = new Px.Math.Vector3(0, 1, 0);
                //indexBuffer[i] = Emdi;
            }*/

            
            var blobOffset = -new Px.Math.Vector3(d) * 0.5f;

            int ii = 0;
            for (int x = 0; x < d; x++)
            {
                for (int y = 0; y < 10; y++)
                {
                    for (int z = 0; z < 10; z++)
                    {
                        // Compute the location in a grid of particles + move the grid back so its center is at 0, 0, 0
                        particlePositions[ii++] = new Px.Math.Vector3(x, 0, y);// +blobOffset;
                    }
                }
            }

            Px.ParticleCreationData data = new Px.ParticleCreationData();
            data.NumberOfParticles = ps.MaximumParticles;
            data.IndexBuffer = Enumerable.Range(0, particlePositions.Length).ToArray();//indexBuffer;
            data.PositionBuffer = particlePositions;
            //data.FlagBuffer = new Px.Math.Vector3[pCount];
            
            
            ps.CreateParticles(data);
            //ps.ParticleMass = 10.0f;
            ps.DynamicFriction = 0.1f;
            ps.StaticFriction = 0.01f;
            ps.Restitution = 1.0f;
            ps.ExternalAcceleration = new Px.Math.Vector3(0, 20, 0);
            
            
            scene.AddActor(ps);
        }

        private void RenderBufferData(Px.RenderBuffer buffer)
        {
            basicEffect.World = Matrix.Identity;
            basicEffect.View = camera.viewMatrix;
            basicEffect.Projection = camera.projectionMatrix;
            basicEffect.VertexColorEnabled = true;

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                if (buffer.NumberOfPoints > 0)
                {
                    vertices = new VertexPositionColor[buffer.Points.Length];//new VertexPositionColor[data.Points.Length];
                    for (int i = 0; i < buffer.Points.Length; i++)
                    {
                        Px.DebugPoint point = buffer.Points[i];

                        vertices[i * 2 + 0] = new VertexPositionColor(point.Point.As<Vector3>(), UnpackColorARGB(point.Color));
                        vertices[i * 2 + 1] = new VertexPositionColor(point.Point.As<Vector3>(), UnpackColorARGB(point.Color));
                    }

                    vertexBuffer = new VertexBuffer(graphics.GraphicsDevice, VertexPositionColor.VertexDeclaration, vertices.Length, BufferUsage.None);
                    vertexBuffer.SetData<VertexPositionColor>(vertices);
                    graphics.GraphicsDevice.SetVertexBuffer(vertexBuffer);

                    graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, buffer.NumberOfPoints);
                }

                if (buffer.NumberOfLines > 0)
                {
                    vertices = new VertexPositionColor[buffer.Lines.Length * 2];//new VertexPositionColor[data.Points.Length];
                    for (int i = 0; i < buffer.Lines.Length; i++)
                    {
                        Px.DebugLine line = buffer.Lines[i];

                        Vector3 p0 = new Vector3(line.Point0.X, line.Point0.Y, line.Point0.Z);
                        Vector3 p1 = new Vector3(line.Point1.X, line.Point1.Y, line.Point1.Z);

                        vertices[i * 2 + 0] = new VertexPositionColor(p0, UnpackColorARGB(line.Color0));
                        vertices[i * 2 + 1] = new VertexPositionColor(p1, UnpackColorARGB(line.Color1));
                    }

                    vertexBuffer = new VertexBuffer(graphics.GraphicsDevice, VertexPositionColor.VertexDeclaration, vertices.Length, BufferUsage.None);
                    vertexBuffer.SetData<VertexPositionColor>(vertices);
                    graphics.GraphicsDevice.SetVertexBuffer(vertexBuffer);
                    
                    graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, buffer.NumberOfLines);
                }

                if (buffer.NumberOfTriangles > 0)
                {
                    vertices = new VertexPositionColor[buffer.Triangles.Length * 3];//new VertexPositionColor[data.Points.Length];
                    for (int i = 0; i < buffer.Triangles.Length; i++)
                    {
                        Px.DebugTriangle triangle = buffer.Triangles[i];

                        Vector3 p0 = new Vector3(triangle.Point0.X, triangle.Point0.Y, triangle.Point0.Z);
                        Vector3 p1 = new Vector3(triangle.Point1.X, triangle.Point1.Y, triangle.Point1.Z);
                        Vector3 p2 = new Vector3(triangle.Point2.X, triangle.Point2.Y, triangle.Point2.Z);

                        vertices[i * 3 + 0] = new VertexPositionColor(p0, UnpackColorARGB(triangle.Color0));
                        vertices[i * 3 + 1] = new VertexPositionColor(p1, UnpackColorARGB(triangle.Color1));
                        vertices[i * 3 + 2] = new VertexPositionColor(p2, UnpackColorARGB(triangle.Color2));
                    }

                    vertexBuffer = new VertexBuffer(graphics.GraphicsDevice, VertexPositionColor.VertexDeclaration, vertices.Length, BufferUsage.None);
                    vertexBuffer.SetData<VertexPositionColor>(vertices);
                    graphics.GraphicsDevice.SetVertexBuffer(vertexBuffer);

                    graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, buffer.NumberOfTriangles);
                }
            }
        }

        private Color UnpackColorARGB(int color)
        {
            Color unpackedColor = new Color();
            unpackedColor.B = (byte)(color);
            unpackedColor.G = (byte)(color >> 8);
            unpackedColor.R = (byte)(color >> 16);
            unpackedColor.A = (byte)(color >> 24);

            return unpackedColor;
        }

        #endregion
    }
}
